package com.simpleplayer.engine;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.TextureView;

import androidx.annotation.NonNull;

import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.TracksInfo;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.video.VideoSize;
import com.simpleplayer.services.LogUtil;

class ExoPlayerEngine extends PlayerEngine {

    private final static String TAG = "ExoPlayerEngine";

    private final Context mContext;
    private final Handler mMainHandler;
    private ExoPlayer mPlayer;
    private DefaultBandwidthMeter mBandwidthMeter;

    private int mVideoHeight = 0;
    private int mVideoWidth = 0;
    private long mDuration = 0;
    private int mPlaybackState;

    public ExoPlayerEngine(Context context) {
        mContext = context;
        mMainHandler = new Handler();
        ensurePlayerCreated();
    }

    private void ensurePlayerCreated() {
        if(mPlayer == null) {
            mBandwidthMeter = new DefaultBandwidthMeter.Builder(mContext).build();
            mPlayer = new ExoPlayer.Builder(mContext).setBandwidthMeter(mBandwidthMeter).build();
            mPlayer.addListener(new ExoPlayerListener());
        }
    }

    private ExoPlayer getPlayer() {
        ensurePlayerCreated();
        return mPlayer;
    }

    @Override
    public long getBandwidth() {
        ensurePlayerCreated();
        return mBandwidthMeter.getBitrateEstimate();
    }

    @Override
    public long getCurrentPosition() {
        return Math.min(getPlayer().getCurrentPosition(), getDuration());
    }

    @Override
    public long getDuration() {
        return mDuration;
    }

    @Override
    public void prepare(String uri) {
        MediaItem mediaItem = MediaItem.fromUri(uri);
        getPlayer().setMediaItem(mediaItem);
        getPlayer().prepare();
        getPlayer().setPlayWhenReady(false);
    }

    @Override
    public void play() {
        getPlayer().play();
    }

    @Override
    public void pause() {
        getPlayer().pause();
    }

    @Override
    public void release() {
        getPlayer().release();
    }

    @Override
    public void seekTo(long position) {
        getPlayer().seekTo(position);
        getClient().onSeekComplete();
    }

    @Override
    public void setSurface(Surface surface) {
        if (!isMainThread()) {
            LogUtil.w(TAG, "setVideoSurface() is called on child thread, it's better to call this method" +
                    "on main thread, and we will do this delay");
        }

        postOnMainThread(() -> {
            if (mPlayer != null) {
                LogUtil.d(TAG, "setVideoSurface " + surface);
                getPlayer().setVideoSurface(surface);
            }
        });
    }

    @Override
    public void setDisplay(SurfaceHolder holder) {
        if (!isMainThread()) {
            LogUtil.w(TAG, "setVideoSurfaceHolder() is called on child thread, it's better to call this method" +
                    "on main thread, and we will do this delay");
        }

        postOnMainThread(() -> {
            if (mPlayer != null) {
                LogUtil.d(TAG, "setVideoSurfaceHolder " + holder);
                getPlayer().setVideoSurfaceHolder(holder);
            }
        });
    }

    @Override
    public void setSurfaceView(SurfaceView surfaceView) {
        if (!isMainThread()) {
            LogUtil.w(TAG, "setVideoSurfaceHolder() is called on child thread, it's better to call this method" +
                    "on main thread, and we will do this delay");
        }

        postOnMainThread(() -> {
            if (mPlayer != null) {
                LogUtil.d(TAG, "setSurfaceView " + surfaceView);
                getPlayer().setVideoSurfaceView(surfaceView);
            }
        });
    }

    @Override
    public void setTextureView(TextureView textureView) {
        if (!isMainThread()) {
            LogUtil.w(TAG, "setVideoSurfaceHolder() is called on child thread, it's better to call this method" +
                    "on main thread, and we will do this delay");
        }

        postOnMainThread(() -> {
            if (mPlayer != null) {
                LogUtil.d(TAG, "setTextureView " + textureView);
                getPlayer().setVideoTextureView(textureView);
            }
        });
    }

    @Override
    public int getVideoHeight() {
        return mVideoHeight;
    }

    @Override
    public int getVideoWidth() {
        return mVideoWidth;
    }

    @Override
    public void setVolume(float volume) {
        getPlayer().setVolume(volume);
    }

    private boolean isMainThread() {
        return Looper.myLooper() == Looper.getMainLooper();
    }

    private void postOnMainThread(Runnable runnable) {
        if (isMainThread()) {
            // 已经在主线程，直接运行
            runnable.run();
        } else {
            // 在子线程，抛到主线程去运行
            mMainHandler.post(runnable);
        }
    }

    private class ExoPlayerListener implements Player.Listener {
        @Override
        public void onTracksInfoChanged(@NonNull TracksInfo tracksInfo) {
            postOnMainThread(() -> {
                if (mPlayer == null) return;
                mDuration = getPlayer().getDuration();
                getClient().onPrepared();
            });
        }

        @Override
        public void onPositionDiscontinuity(
                @NonNull Player.PositionInfo oldPosition, @NonNull Player.PositionInfo newPosition, int reason) {
            postOnMainThread(() -> {
                switch (reason) {
                    case Player.DISCONTINUITY_REASON_SEEK:
                        getClient().onSeekComplete();
                        break;
                    case Player.DISCONTINUITY_REASON_AUTO_TRANSITION:
                    case Player.DISCONTINUITY_REASON_INTERNAL:
                    case Player.DISCONTINUITY_REASON_REMOVE:
                    case Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT:
                    case Player.DISCONTINUITY_REASON_SKIP:
                        break;
                }
            });
        }

        @Override
        public void onPlayerError(@NonNull PlaybackException error) {
            postOnMainThread(() -> getClient().onError());
        }

        @Override
        public void onVideoSizeChanged(VideoSize videoSize) {
            mVideoHeight = videoSize.height;
            mVideoWidth = videoSize.width;
            if (videoSize.width > 0 && videoSize.height > 0 && videoSize.pixelWidthHeightRatio > 0) {
                float realVideoAspectRatio = (float)(videoSize.width)/(float)(videoSize.height);
                float targetVideoAspectRatio = realVideoAspectRatio * videoSize.pixelWidthHeightRatio;
                if (videoSize.pixelWidthHeightRatio > 1.0) {
                    mVideoHeight = (int) (videoSize.width / targetVideoAspectRatio);
                } else {
                    mVideoWidth = (int) (videoSize.height * targetVideoAspectRatio);
                }
            }
            postOnMainThread(() -> getClient().onVideoSizeChanged(mVideoWidth, mVideoHeight));
        }

        @Override
        public void onPlaybackStateChanged(int playbackState) {
            postOnMainThread(() -> {
                int oldState = mPlaybackState;
                mPlaybackState = playbackState;

                switch(playbackState) {
                    case Player.STATE_IDLE:
                        break;
                    case Player.STATE_BUFFERING:
                        getClient().onBufferingStart();
                        break;
                    case Player.STATE_READY:
                        getClient().onBufferingEnd();
                        break;
                    case Player.STATE_ENDED:
                        if (oldState == Player.STATE_BUFFERING) {
                            getClient().onBufferingEnd();
                        }
                        getClient().onPlaybackComplete();
                        break;
                    default:
                        LogUtil.e(TAG, "Unknown playback state " + playbackState);
                        break;
                }
            });
        }
    }
}
